﻿
#region Copyright & License
// --------------------------
// Copyright 2008 Daniel Gray
// --------------------------
// This file is part of Kale.
//
// Kale is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Kale is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Kale.  If not, see <http://www.gnu.org/licenses/>.
#endregion
#region File details
// --------------------------------------------------------
// Filename          : SecurityProvider.cs
// Author            : Daniel Gray
// Created           : 30/04/2008
// --------------------------------------------------------
#endregion
            
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kale.Core.Security;
using Kale.Core.Configuration;

namespace Kale.Db.Sql
{
   /// <summary>
   /// Provides an implementation of an ISecurityProvider backed onto a SQL Server DB using Linq.
   /// </summary>
   [RequiresConnectionString]
   public class SecurityProvider : BaseSqlProvider, ISecurityProvider
   {
      
      /// <summary>
      /// Initializes a new instance of the <see cref="SqlSecurityProvider"/> class.
      /// </summary>
      /// <param name="configurationDictionary">The configuration dictionary.</param>
      public SecurityProvider(Dictionary<string,string> configurationDictionary)
      {
         LoadSqlConfigurationValuesFromDictionary(configurationDictionary);
      }

      private void CacheRoles(IQueryable<UserRole> roles)
      {
         foreach (UserRole role in roles)
         {
            lock (UserRoleCache)
            {
               if (!UserRoleCache.ContainsKey(role.Uid))
               {
                  UserRoleCache.Add(role.Uid, role);
               }
            }
         }
      }

      private void CacheGroups(IQueryable<UserGroup> groups)
      {
         foreach (UserGroup group in groups)
         {
            lock (UserGroupCache)
            {
               if (!UserGroupCache.ContainsKey(group.Uid))
               {
                  group.Provider = this;
                  UserGroupCache.Add(group.Uid, group);
               }
            }
         }
      }

      private void CacheUsers(IQueryable<User> users)
      {
         foreach (User user in users)
         {
            lock (UserCache)
            {
               if (!UserCache.ContainsKey(user.Uid))
               {
                  user.Provider = this;
                  UserCache.Add(user.Uid, user);
               }
            }
         }
      }

      private Dictionary<Guid, User> _userCache = new Dictionary<Guid, User>();
      private Dictionary<Guid, UserGroup> _userGroupCache = new Dictionary<Guid, UserGroup>();
      private Dictionary<Guid, UserRole> _userRoleCache = new Dictionary<Guid, UserRole>();

      private Dictionary<Guid, User> UserCache
      {
         get { return _userCache; }
      }

      private Dictionary<Guid, UserGroup> UserGroupCache
      {
         get { return _userGroupCache; }
      }

      private Dictionary<Guid, UserRole> UserRoleCache
      {
         get { return _userRoleCache; }
      }

      #region ISecurityProvider Members

      public void ClearCaches()
      {
         lock (UserCache) UserCache.Clear();
         lock (UserGroupCache) UserGroupCache.Clear();
         lock (UserRoleCache) UserRoleCache.Clear();
      }

      /// <summary>
      /// Gets the user with the specified username.
      /// </summary>
      /// <param name="userName">The username of the user to retrieve.</param>
      /// <returns>
      /// An instance of <see cref="IUser"/> if the user exists, otherwise <b>null</b>.
      /// </returns>
      public IUser GetUser(string userName)
      {
         lock (UserCache)
         {
            if (UserCache.Values.Count(v => v.Name == userName) > 0)
            {
               return UserCache.Values.Where(v => v.Name == userName).FirstOrDefault() as IUser;
            }
         }

         // get from the db and then add to the user cache if no-one else has
         using (KaleModelDataContext context = GetContext())
         {
            User user = context.Users.Where(u => u.Name == userName).FirstOrDefault();

            if (user != null)
            {
               lock (UserCache)
               {
                  if (UserCache.Values.Count(v => v.Name == userName) == 0)
                  {
                     UserCache.Add(user.Uid, user);
                     user.Provider = this;
                  }

                  return user as IUser;
               }
            }
            else
            {
               return null;
            }
         }
      }

      /// <summary>
      /// Gets the user with the specified Uid.
      /// </summary>
      /// <param name="userUid">The Uid of the user to retrieve.</param>
      /// <returns>
      /// An instance of <see cref="IUser"/> if the user exists, otherwise <b>null</b>.
      /// </returns>
      public IUser GetUser(Guid userUid)
      {
         lock (UserCache)
         {
            if (UserCache.ContainsKey(userUid))
            {
               return UserCache[userUid];
            }
         }

         // get from the db and then add to the user cache if no-one else has
         using (KaleModelDataContext context = GetContext())
         {
            User user = context.Users.Where(u => u.Uid == userUid).FirstOrDefault();

            if (user != null)
            {
               lock (UserCache)
               {
                  if (!UserCache.ContainsKey(user.Uid))
                  {
                     UserCache.Add(user.Uid, user);
                     user.Provider = this;
                  }

                  return user as IUser;
               }
            }
            else
            {
               return null;
            }
         }
      }

      /// <summary>
      /// Gets the user group with the specified name.
      /// </summary>
      /// <param name="userGroupName">The name of the group to retrieve.</param>
      /// <returns>
      /// An instance of <see cref="IUserGroup"/> if the group exists, otherwise <b>null</b>.
      /// </returns>
      public IUserGroup GetUserGroup(string userGroupName)
      {
         lock (UserGroupCache)
         {
            if (UserGroupCache.Values.Count(v => v.Name == userGroupName) > 0)
            {
               return UserGroupCache.Values.Where(v => v.Name == userGroupName).FirstOrDefault();
            }
         }

         // get from the db and then add to the user-group cache if no-one else has
         using (KaleModelDataContext context = GetContext())
         {
            UserGroup group = context.UserGroups.Where(u => u.Name == userGroupName).FirstOrDefault();

            if (group != null)
            {
               lock (UserGroupCache)
               {
                  if (UserGroupCache.Values.Count(v => v.Name == userGroupName) == 0)
                  {
                     UserGroupCache.Add(group.Uid, group);
                     group.Provider = this;
                  }

                  return group as IUserGroup;
               }
            }
            else
            {
               return null;
            }
         }
      }

      /// <summary>
      /// Gets the user group with the specified Uid.
      /// </summary>
      /// <param name="userGroupUid">The Uid of the user group to retrieve.</param>
      /// <returns>
      /// An instance of <see cref="IUserGroup"/> if the group exists, otherwise <b>null</b>.
      /// </returns>
      public IUserGroup GetUserGroup(Guid userGroupUid)
      {
         lock (UserGroupCache)
         {
            if (UserGroupCache.ContainsKey(userGroupUid))
            {
               return UserGroupCache[userGroupUid];
            }
         }

         // get from the db and then add to the user-group cache if no-one else has
         using (KaleModelDataContext context = GetContext())
         {
            UserGroup group = context.UserGroups.Where(u => u.Uid == userGroupUid).FirstOrDefault();

            if (group != null)
            {
               lock (UserGroupCache)
               {
                  if (!UserGroupCache.ContainsKey(userGroupUid))
                  {
                     UserGroupCache.Add(group.Uid, group);
                     group.Provider = this;
                  }

                  return group as IUserGroup;
               }
            }
            else
            {
               return null;
            }
         }
      }

      /// <summary>
      /// Gets the user role with the specified name.
      /// </summary>
      /// <param name="userRoleName">The name of the role to retrieve.</param>
      /// <returns>
      /// An instance of <see cref="IUserRole"/> if the role exists, otherwise <b>null</b>.
      /// </returns>
      public IUserRole GetUserRole(string userRoleName)
      {
         lock (UserRoleCache)
         {
            if (UserRoleCache.Values.Count(v => v.Name == userRoleName) > 0)
            {
               return UserRoleCache.Values.Where(v => v.Name == userRoleName).FirstOrDefault();
            }
         }

         // get from the db and then add to the user-group cache if no-one else has
         using (KaleModelDataContext context = GetContext())
         {
            UserRole role = context.UserRoles.Where(u => u.Name == userRoleName).FirstOrDefault();

            if (role != null)
            {
               lock (UserRoleCache)
               {
                  if (UserRoleCache.Values.Count(v => v.Name == userRoleName) == 0)
                  {
                     UserRoleCache.Add(role.Uid, role);
                  }

                  return role as IUserRole;
               }
            }
            else
            {
               return null;
            }
         }
      }

      /// <summary>
      /// Gets the user role with the specified Uid.
      /// </summary>
      /// <param name="userRoleUid">The Uid of the role to retrieve.</param>
      /// <returns>
      /// An instance of <see cref="IUserRole"/> if the role exists, otherwise <b>null</b>.
      /// </returns>
      public IUserRole GetUserRole(Guid userRoleUid)
      {
         lock (UserRoleCache)
         {
            if (UserRoleCache.ContainsKey(userRoleUid))
            {
               return UserRoleCache[userRoleUid];
            }
         }

         // get from the db and then add to the user-group cache if no-one else has
         using (KaleModelDataContext context = GetContext())
         {
            UserRole role = context.UserRoles.Where(u => u.Uid == userRoleUid).FirstOrDefault();

            if (role != null)
            {
               lock (UserRoleCache)
               {
                  if (!UserRoleCache.ContainsKey(userRoleUid))
                  {
                     UserRoleCache.Add(role.Uid, role);
                  }

                  return role as IUserRole;
               }
            }
            else
            {
               return null;
            }
         }
      }

      /// <summary>
      /// Gets all the user groups the specified user explicitly belongs to.
      /// </summary>
      /// <param name="userUid">The Uid of the user.</param>
      /// <returns>An array of <see cref="IUserGroup"/>.</returns>
      public IUserGroup[] GetUserGroupsForUser(Guid userUid)
      {
         using (KaleModelDataContext context = GetContext())
         {
            var groups = from u in context.Users
                         join u_ug in context.User_UserGroups on u.Uid equals u_ug.UserUid
                         join ug in context.UserGroups on u_ug.UserGroupUid equals ug.Uid
                         where u.Uid == userUid
                         select ug;

            CacheGroups(groups);

            return groups.ToArray();
         }
      }

      /// <summary>
      /// Gets all the user roles the specified user explicitly has.
      /// </summary>
      /// <param name="userUid">The Uid of the user.</param>
      /// <returns>An array of <see cref="IUserRole"/>.</returns>
      public IUserRole[] GetUserRolesForUser(Guid userUid)
      {
         using (KaleModelDataContext context = GetContext())
         {
            var roles = from u in context.Users
                        join u_ur in context.User_UserRoles on u.Uid equals u_ur.UserUid
                        join ur in context.UserRoles on u_ur.UserRoleUid equals ur.Uid
                        where u.Uid == userUid
                        select ur;

            CacheRoles(roles);

            return roles.ToArray();
         }
      }

      /// <summary>
      /// Gets all the user groups the specified user group explicitly belongs to.
      /// </summary>
      /// <param name="userGroupUid">The Uid of the user group.</param>
      /// <returns>An array of <see cref="IUserGroup"/>.</returns>
      public IUserGroup[] GetUserGroupsForUserGroup(Guid userGroupUid)
      {
         using (KaleModelDataContext context = GetContext())
         {
            var groups = from ug1 in context.UserGroups
                         join ug_ug in context.UserGroup_UserGroups on ug1.Uid equals ug_ug.ChildUserGroupUid
                         join ug2 in context.UserGroups on ug_ug.ParentUserGroupUid equals ug2.Uid
                         where ug1.Uid == userGroupUid
                         select ug2;

            CacheGroups(groups);

            return groups.ToArray();
         }
      }

      /// <summary>
      /// Gets all the roles the specified user group explicitly has.
      /// </summary>
      /// <param name="userGroupUid"></param>
      /// <returns></returns>
      public IUserRole[] GetUserRolesForUserGroup(Guid userGroupUid)
      {
         using (KaleModelDataContext context = GetContext())
         {
            var roles = from ug in context.UserGroups
                        join ug_ur in context.UserGroup_UserRoles on ug.Uid equals ug_ur.UserGroupUid
                        join ur in context.UserRoles on ug_ur.UserRoleUid equals ur.Uid
                        where ug.Uid == userGroupUid
                        select ur;

            CacheRoles(roles);

            return roles.ToArray();
         }
      }

      /// <summary>
      /// Gets all the user groups.
      /// </summary>
      /// <returns>An array of <see cref="IUserGroup"/>.</returns>
      public IUserGroup[] GetUserGroups()
      {
         using (KaleModelDataContext context = GetContext())
         {
            var groups = from ug in context.UserGroups
                         select ug;

            CacheGroups(groups);

            return groups.ToArray();
         }
      }

      /// <summary>
      /// Gets all the user roles.
      /// </summary>
      /// <returns>An array of <see cref="IUserRole"/>.</returns>
      public IUserRole[] GetUserRoles()
      {
         using (KaleModelDataContext context = GetContext())
         {
            var roles = from ur in context.UserRoles
                        select ur;

            CacheRoles(roles);

            return roles.ToArray();
         }
      }

      /// <summary>
      /// Gets all the users.
      /// </summary>
      /// <returns>An array of <see cref="IUser"/>.</returns>
      public IUser[] GetUsers()
      {
         using (KaleModelDataContext context = GetContext())
         {
            var users = from u in context.Users
                        select u;

            CacheUsers(users);

            return users.ToArray();
         }
      }

      #endregion
   }
}
